import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    // ServerSocket 给服务器使用的，负责把连接到服务器的客户端进行统一处理，处理客户端的连接
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("server start!");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while(true){ 
            /* 首先要先接受客户端的连接，然后才能进行通信
             如果有客户端和服务器建立好了连接，accept能够返回，否则accept会阻塞
             每次有客户端连上服务器，都会触发一次accept，都会创建一个socket，就会占用一个文件描述符表的位置.
             所以这个socket使用完需要关闭 */
            // [有连接]
            Socket socket = serverSocket.accept();
            // 通过这个方法处理这个客户端整个的连接过程
            // 直接调用 processConnection，此时就会 "顾此失彼" 一旦进入到 processConnection 方法
            // 就不能再次调用 accept
            //processConnection(socket);

            // 此处创建新线程. 在新线程里，调用 processConnection
//            Thread t = new Thread(() -> {
//                try {
//                    processConnection(socket);
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//            });
//            t.start();

            // 如果当前线程数目进一步增多，创建销毁进一步频繁，此时线程创建销毁开销不可忽视了
            // 使用线程池，是进一步的改进手段
            executorService.submit(()->{
                try {
                    processConnection(socket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }

    private void processConnection(Socket socket) throws IOException {
        // 在一次连接中，客户端和服务器之间可能会进行多组数据传输
        // 对于TCP来说，对端的IP和端口就保存在socket中（建立连接，就是要保存对方IP和端口号等信息）
        // UDP的 DatagramSocket 没有保存对端信息，对端信息是从 DatagramPacket 中来的
        System.out.printf("[%s,%d] 客户端上线！\n",socket.getInetAddress(),socket.getPort());
        // [面向字节流] & [全双工]
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {

            Scanner scanner = new Scanner(inputStream); //Scanner帮我们做了数据解析、校验工作
            PrintWriter writer = new PrintWriter(outputStream);

            while(true){
                // 处理多次请求/响应的读写操作.
                // 一次循环就是读写一个请求/响应
                // 1. 读取请求并解析（可以直接使用 Scanner 完成）
                if(!scanner.hasNext()){
                    // 客户端关闭了连接
                    System.out.printf("[%s,%d] 客户端下线！\n",socket.getInetAddress(),socket.getPort());
                    break;
                }
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应返回客户端
                writer.println(response);
                writer.flush();
                // 4. 打印日志
                System.out.printf("[%s,%d] req: %s, resp: %s\n",socket.getInetAddress(),
                        socket.getPort(), request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            socket.close();
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}
